package com.atguigu.daijia.map.service.impl;

import com.atguigu.daijia.common.constant.RedisConstant;
import com.atguigu.daijia.common.constant.SystemConstant;
import com.atguigu.daijia.common.util.LocationUtil;
import com.atguigu.daijia.driver.client.DriverInfoFeignClient;
import com.atguigu.daijia.map.repository.OrderServiceLocationRepository;
import com.atguigu.daijia.map.service.LocationService;
import com.atguigu.daijia.model.entity.driver.DriverSet;
import com.atguigu.daijia.model.entity.map.OrderServiceLocation;
import com.atguigu.daijia.model.form.map.OrderServiceLocationForm;
import com.atguigu.daijia.model.form.map.SearchNearByDriverForm;
import com.atguigu.daijia.model.form.map.UpdateDriverLocationForm;
import com.atguigu.daijia.model.form.map.UpdateOrderLocationForm;
import com.atguigu.daijia.model.vo.map.NearByDriverVo;
import com.atguigu.daijia.model.vo.map.OrderLocationVo;
import com.atguigu.daijia.model.vo.map.OrderServiceLastLocationVo;
import com.atguigu.daijia.order.client.OrderInfoFeignClient;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private DriverInfoFeignClient driverInfoFeignClient;
    @Resource
    private OrderServiceLocationRepository orderServiceLocationRepository;
    @Resource
    private MongoTemplate mongoTemplate;
    @Resource
    private OrderInfoFeignClient orderInfoFeignClient;

    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(),
                updateDriverLocationForm.getLatitude().doubleValue());
        redisTemplate.opsForGeo().add(RedisConstant.DRIVER_GEO_LOCATION,point,
                updateDriverLocationForm.getDriverId().toString());
        return true;
    }

    @Override
    public Boolean removeDriverLocation(Long driverId) {
        redisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION, driverId.toString());
        return true;
    }

    /**
     * 搜索附近的司机
     *
     * @param searchNearByDriverForm 包含搜索条件的表单，如经度和纬度
     * @return 一个包含附近司机信息的列表，如果找不到任何司机则返回空列表
     *
     * 本方法通过Redis的地理位置数据结构来查找指定范围内的司机
     * 它首先根据提供的经纬度和预设半径构建一个圆形区域，然后查询在这个圆形区域内的所有司机
     * 查询结果包括司机的ID和他们与查询点的距离
     * 只有当司机的接受距离和订单距离大于0时，才会被添加到结果列表中
     */
    @Override
    public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
        // 创建一个点，表示司机的当前位置（经度和纬度）
        Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(),
                searchNearByDriverForm.getLatitude().doubleValue());
        // 创建一个距离对象，表示搜索的半径
        Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS, RedisGeoCommands.DistanceUnit.KILOMETERS);
        // 创建一个圆，表示搜索的区域
        Circle circle = new Circle(point, distance);
        // 设置查询参数，包括坐标、距离、排序方式等
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs.
                newGeoRadiusArgs().
                includeCoordinates().
                includeDistance().
                sortAscending();
        // 执行Redis地理查询，获取附近的司机
        GeoResults<RedisGeoCommands.GeoLocation<String>> result = redisTemplate.opsForGeo().
                radius(RedisConstant.DRIVER_GEO_LOCATION, circle, geoRadiusCommandArgs);
        // 获取查询结果的内容
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = result.getContent();
        // 初始化一个列表，用于存储附近的司机信息
        List<NearByDriverVo> nearByDriverVoList = new ArrayList<>();
        // 如果查询结果不为空
        if(!CollectionUtils.isEmpty(content)){
            // 遍历查询结果
            Iterator<GeoResult<RedisGeoCommands.GeoLocation<String>>> iterator = content.iterator();
            while(iterator.hasNext()){
                // 获取当前的查询结果项
                GeoResult<RedisGeoCommands.GeoLocation<String>> item = iterator.next();
                // 解析出司机的ID
                Long driverId = Long.parseLong(item.getContent().getName());
                // 计算并格式化司机与查询点的距离
                BigDecimal currentDistance = new BigDecimal(item.getDistance().getValue()).
                        setScale(2, BigDecimal.ROUND_HALF_UP);
                // 调用Feign客户端获取司机的设置信息
                DriverSet driverSet = driverInfoFeignClient.getDriverSet(driverId).getData();
                // 检查司机的接受距离，如果小于当前距离则跳过
                if(driverSet.getAcceptDistance().doubleValue() != 0
                        && driverSet.getAcceptDistance().subtract(currentDistance).doubleValue() < 0){
                    continue;
                }
                // 检查司机的订单距离，如果小于当前距离则跳过
                if(driverSet.getOrderDistance().doubleValue() != 0 &&
                        driverSet.getOrderDistance().subtract(currentDistance).doubleValue() < 0){
                    continue;
                }
                // 创建附近的司机VO对象，并设置其属性
                NearByDriverVo nearByDriverVo = new NearByDriverVo();
                nearByDriverVo.setDriverId(driverId);
                nearByDriverVo.setDistance(currentDistance);
                // 将司机信息添加到结果列表中
                nearByDriverVoList.add(nearByDriverVo);
            }
        }
        // 返回附近的司机列表
        return nearByDriverVoList;
    }

    /**
     * 更新司机位置到缓存
     */
    @Override
    public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {
        // 创建OrderLocationVo对象，用于存储订单位置信息
        OrderLocationVo orderLocationVo = new OrderLocationVo();

        // 设置订单的纬度信息
        orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());

        // 设置订单的经度信息
        updateOrderLocationForm.setLongitude(updateOrderLocationForm.getLongitude());

        // 将订单位置信息保存到Redis中，使用特定的键名格式
        redisTemplate.opsForValue().set(RedisConstant.UPDATE_ORDER_LOCATION + updateOrderLocationForm.getOrderId(),
                orderLocationVo);

        // 返回更新操作成功标志
        return true;
    }

    /**
     * 获取司机位置
     */
    @Override
    public OrderLocationVo getCacheOrderLocation(Long orderId) {
        return (OrderLocationVo) redisTemplate.opsForValue().get(RedisConstant.UPDATE_ORDER_LOCATION + orderId);
    }

    @Override
    public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {
        List<OrderServiceLocation> list = new ArrayList<>();
        orderLocationServiceFormList.forEach(item -> {
            OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
            BeanUtils.copyProperties(item, orderServiceLocation);
            orderServiceLocation.setId(ObjectId.get().toString());
            orderServiceLocation.setCreateTime(new Date());
        });
        orderServiceLocationRepository.saveAll(list);
        return true;
    }

    @Override
    public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("orderId").is(orderId));
        query.with(Sort.by(Sort.Order.desc("createTime")));
        query.limit(1);
        OrderServiceLocation orderServiceLocation = mongoTemplate.findOne(query, OrderServiceLocation.class);
        OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
        if(orderServiceLocation != null) {
            BeanUtils.copyProperties(orderServiceLocation, orderServiceLastLocationVo);
        }
        return orderServiceLastLocationVo;
    }

    /**
     * 计算订单的实际行驶距离
     * 如果数据库中有记录，则计算所有服务地点之间的实际距离总和
     * 如果数据库中没有记录，则调用Feign客户端获取预期距离并加上模拟的实际驾驶调整值
     *
     * @param orderId 订单ID
     * @return 实际行驶距离，返回BigDecimal类型以确保精度
     */
    @Override
    public BigDecimal calculateOrderRealDistance(Long orderId) {
        // 创建查询对象
        Query query = new Query();
        // 设置查询条件，查找特定订单ID的记录
        query.addCriteria(Criteria.where("orderId").is(orderId));
        // 按照创建时间升序排序
        query.with(Sort.by(Sort.Order.asc("createTime")));
        // 执行查询，获取订单服务位置列表
        List<OrderServiceLocation> list = mongoTemplate.find(query, OrderServiceLocation.class);

        // 初始化实际行驶距离
        double realDistance = 0;
        // 如果列表不为空，则计算所有服务地点之间的实际距离总和
        if (!CollectionUtils.isEmpty(list)) {
            // 遍历列表，计算相邻两个服务地点之间的距离并累加
            for (int i = 0; i < list.size() - 1; i++) {
                OrderServiceLocation location1 = list.get(i);
                OrderServiceLocation location2 = list.get(i + 1);
                // 使用工具类计算两个地理位置之间的距离
                double distance = LocationUtil.getDistance(
                        location1.getLatitude().doubleValue(),
                        location1.getLongitude().doubleValue(),
                        location2.getLatitude().doubleValue(),
                        location2.getLongitude().doubleValue());
                // 累加距离
                realDistance += distance;
            }
        }

        // 如果实际行驶距离为0，则通过Feign客户端获取订单信息，并加上模拟的实际驾驶调整值
        if (realDistance == 0) {
            // 调用Feign客户端获取订单信息，返回数据中的预期距离加上模拟的实际驾驶调整值
            return orderInfoFeignClient.getOrderInfo(orderId).getData().getExpectDistance().add(new BigDecimal("5"));
        }
        // 返回计算的实际行驶距离，使用BigDecimal确保精度
        return new BigDecimal(realDistance);
    }
}
